﻿#region GNU General Public License
/*
-----------------------------------------------------------------------------
This source file is part of jscc
    (Javascript with Classes)
For the latest info, see http://code.google.com/p/jscc

Copyright (c) 2011 Tapani Valkonen

jscc is free software: you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the 
Free Software Foundation, either version 3 of the License, or (at your option)
any later version. 
 
jscc is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
for more details.

You should have received a copy of the GNU General Public License 
along with jscc. If not, see http://www.gnu.org/licenses/.
-----------------------------------------------------------------------------
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using jscc.Ast;

namespace jscc
{
    public class Parser
    {
        private TokenStream stream;

        public Parser(TokenStream stream)
        {
            this.stream = stream;
        }

        public void Error(string msg)
        {
            throw ParseError.Syntax(msg, Current().Location);
        }

        public bool Expect(KeywordType key)
        {
            if (Accept(key))
                return true;
            Error("Unexpected keyword " + Current().Data);
            return false;
        }

        public bool Expect(TokenType type)
        {
            if (Accept(type))
                return true;
            Error("Unexpected symbol " + Current().Data);
            return false;
        }

        public bool Accept(KeywordType key)
        {
            Token curToken = Current();

            if (curToken.Type == TokenType.Keyword &&
                curToken.Keyword == key)
            {
                Next();
                return true;
            }
            else
                return false;
        }

        public bool Accept(TokenType type)
        {
            Token curToken = Current();
            if (curToken.Type == type)
            {
                Next();
                return true;
            }
            else
                return false;
        }

        public Token Current()
        {
            return stream.Current;
        }

        public void Next()
        {
            stream.MoveNext();
        }

        public AstClass ClassDeclaration()
        {
            Expect(KeywordType.Class);

            AstClass node = new AstClass(ParseIdentifier());

            if (Accept(TokenType.Colon))
            {
                string inherit = Current().Data;
                node.Inherits = ParseIdentifier();
            }

            Expect(TokenType.BraOpen);

            while (Current().Type != TokenType.BraClose &&
                   Current().Type != TokenType.Eof)
            {
                MemberDeclaration(node);
            }

            Expect(TokenType.BraClose);

            return node;
        }

        private string ParseIdentifier()
        {
            string ident = Current().Data;
            Expect(TokenType.Identifier);
            return ident;
        }

        public void ParseDeclarations(AstNode node)
        {
            while (Current().Type != TokenType.BraClose &&
                   Current().Type != TokenType.Eof)
            {
                switch (Current().Keyword)
                {
                    case KeywordType.Class:
                        node.AddChildren(ClassDeclaration());
                        break;

                    default:
                        Error("Expecting class declaration");
                        break;
                }
            }
        }

        public Accesability AccessDefinition()
        {
            switch (Current().Keyword)
            {
                case KeywordType.Public:
                    Next();
                    return Accesability.Public;

                case KeywordType.Private:
                    Next();
                    return Accesability.Private;

                case KeywordType.Static:
                    Next();
                    return Accesability.Static;

                default:
                    return Accesability.Private;
            }
        }

        public void MemberDeclaration(AstClass parent)
        {
            Accesability access = AccessDefinition();
            bool variable = Current().Keyword == KeywordType.Var;
            if (variable) Next();
            string name = ParseIdentifier();

            switch (Current().Type)
            {
                case TokenType.ParOpen:
                    MethodDeclaration(parent, access, name);
                    break;

                case TokenType.Assign:
                    if (!variable) Error("Keyword var missing");
                    VariableDeclaration(parent, access, name);
                    break;

                default:
                    Error("Expecting method or variable declaration");
                    break;
            }
        }

        public void MethodDeclaration(AstClass parent, Accesability access, string name)
        {
            Expect(TokenType.ParOpen);

            string parameters = "";

            while (Current().Type != TokenType.Eof)
            {
                if (Current().Type == TokenType.ParClose) break;
                parameters += Current().Data;
                Next();
            }

            AstMethod node = null;

            if (name == parent.Name)
            {
                node = new AstConstructor(name, access);
                parent.Params = parameters;
            }
            else
            {
                node = new AstMethod(name, access);
                node.Parameters = parameters;
            }

            parent.AddChildren(node);

            Expect(TokenType.ParClose);

            //if (Accept(TokenType.Semi)) return;

            CodeBlock(node);
        }

        public void VariableDeclaration(AstClass parent, Accesability access, string name)
        {
            Next();

            int begin = Current().Location.Begin;

            while (Current().Type != TokenType.Eof &&
                   Current().Type != TokenType.Semi)
            {
                Next();
            }

            if (Current().Type != TokenType.Semi) Error("Incorrect variable assignment");
            else
            {
                SourceLocation loc = Current().Location;
                loc.End = loc.Begin;
                loc.Begin = begin;
                Next();
                AstVariable node = new AstVariable(name, access, loc.Data);
                parent.AddChildren(node);
            }
        }

        public void CodeBlock(AstNode parent)
        {
            Expect(TokenType.BraOpen);

            int beginLoc = Current().Location.Begin;
            SourceLocation currentLoc = null;

            int depth = 0;
            string content = "";

            while (Current().Type != TokenType.Eof)
            {
                currentLoc = Current().Location;

                if (Current().Type == TokenType.BraOpen)
                    depth++;
                else if (Current().Type == TokenType.BraClose)
                    depth--;

                if (depth < 0) break;

                content += Current().Data;

                Next();
            }

            if (depth > 0) Error("Missing closing bracket");

            currentLoc.End = currentLoc.Begin;
            currentLoc.Begin = beginLoc;
            AstCodeblock block = new AstCodeblock(currentLoc);
            parent.AddChildren(block);

            Expect(TokenType.BraClose);
        }

        public AstNode Parse()
        {
            AstNode node = new AstNode("Root");

            Next();

            try
            {
                ParseDeclarations(node);                
            }
            catch (ParseError error)
            {
                Console.WriteLine(error.ToString());
            }
            
            return node;
        }
    }
}
